package edu.kit.los.servlets;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLStreamException;

import com.evdb.javaapi.EVDBRuntimeException;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;

import edu.kit.los.Carrier;
import edu.kit.los.Model;


/**
 * 
 * @author Felix Kiechle, Benedikt Fuss AIFB@KIT 
 * Zentrales Servlet, welches alle ankommenden Anfragen routet.
 * Hierfür existieren zwei Möglichkeiten.
 * a) es wird der Accept-Header des Requests untersucht und dementsprechend
 * die Ausgabe gewählt und der Content-Type gesetzt
 * b) der Content-Type wird durch die Angabe des format-Parameters
 * in der URI überschrieben
 * 
 */
public class Servlet extends javax.servlet.http.HttpServlet {

	private static final long serialVersionUID = 4535760428976026768L;
	//Strings im format-Parameter
	public static final String format_HTML_map = "map";
	public static final String format_HTML_table = "table";
	public static final String format_KML = "kml";
	public static final String format_RDF = "rdf";
	//verwendete Content-Types
	public static final String contentType_HTML = "text/html";//Karte/Map
	public static final String contentType_KML = "application/vnd.google-earth.kml+xml";//KML
	public static final String contentType_RDF = "application/rdf+xml";//RDF
	public static final String contentType_XML = "text/xml";//Tabelle
	//Standardwert für das Limit, falls nicht anders gesetzt
	public static final String standartLimit = "20";
	//Das Model, in welches die Events geschrieben werden
	Model model = null;
	
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String contentType = null;
		//Parameter einlesen
		String format = request.getParameter("format");
		//Content-Type aus Accept-Header, falls Parameter format nicht gesetzt wurde
		if (format == null){
			//ContentType über Accept-Header Bestimmen
			String accept_header = request.getHeader("Accept");
			StringTokenizer st = new StringTokenizer(accept_header, ",");
			while(st.hasMoreTokens()){
				contentType = st.nextToken();
				if (contentType.equals(contentType_HTML)||
						contentType.equals(contentType_KML)||
						contentType.equals(contentType_RDF)||
						contentType.equals(contentType_XML)) break;
			}
		}
		//falls Parameter format gesetzt wurde, damit den Eintrag aus dem Acccept-Header überschreiben
		else{
			if(format.equalsIgnoreCase(format_HTML_map))contentType=contentType_HTML;
			else if(format.equalsIgnoreCase(format_HTML_table))contentType=contentType_XML;
			else if(format.equalsIgnoreCase(format_KML))contentType=contentType_KML;
			else if(format.equalsIgnoreCase(format_RDF))contentType=contentType_RDF;
		}
		//Standardwert
		if(contentType==null)contentType = contentType_RDF;
		// Weiterleitung an Geocodingservice
		if (request.getParameter("geo") != null) {
			RequestDispatcher dispatcher = request.getRequestDispatcher("geo");
			dispatcher.forward(request, response);
		} 
		else {
			OutputStream out;
			//Parameter-Map erzeugen
			Map<String, String> param = parameters(request);
			//Parameter einlesen
			String query = param.get("query");
			int limit = Integer.parseInt(param.get("limit"));
			//Liste der Carrier erzeugen
			String[] source = request.getParameterValues("source");
			List<Carrier> sourcelist= new LinkedList<Carrier>();
			if(source!=null && source.length!=0){
				for (int i = 0; i < source.length; i++){
					String str = source[i].toUpperCase();
					try{
						Carrier c = Carrier.valueOf(str);
						if (c!=null) sourcelist.add(c);
					}
					catch(IllegalArgumentException e){
					}
				}
			}
			// Ausgabe in verschiedenen Content-Types
			try {
				// Ausgabe eines KML-Files
				if (contentType.equalsIgnoreCase(contentType_KML)) {
					response.setContentType(contentType_KML);
					model = new edu.kit.los.Model();
					lookup(query, limit, sourcelist);
					out = response.getOutputStream();
					model.writeKML(out);
				}
				// Ausgabe als RDF
				else if (contentType.equalsIgnoreCase(contentType_RDF)) {
					response.setContentType(contentType_RDF);
					model = new edu.kit.los.Model();
					lookup(query, limit, sourcelist);
					out = response.getOutputStream();
					model.writeRDF(out);
				}
				// Weiterleitung zu map.jsp zur Darstellung in GoogleMaps
				else if (contentType.equalsIgnoreCase(contentType_HTML)){
					RequestDispatcher dispatcher = request.getRequestDispatcher("map.jsp");
					dispatcher.forward(request, response);
				}
				// Ausgabe einer HTML-Tabelle
				else{
					response.setContentType(contentType_XML);
					model = new edu.kit.los.Model();
					lookup(query, limit, sourcelist);
					out = response.getOutputStream();
					model.writeHTML_table(out);
				}
			} 
			catch (XMLStreamException xmlE) {
				xmlE.printStackTrace(System.out);
			}
			// Error Handling für EventFul Time-Outs
			catch (EVDBRuntimeException evde) {
				out = response.getOutputStream();
				writeError(out, query, format);
				evde.printStackTrace();
			}
			// Error Handling für Facebook Time-Outs
			catch (java.net.SocketTimeoutException evde) {
				out = response.getOutputStream();
				writeError(out, query, format);
				evde.printStackTrace();
			}
		}
	}
	
	/**
	 * Je nach Wahl des Benutzers werden asynchron verschiedene Lookups durchgeführt 
	 * und in das Model hinzugefügt. 
	 * 
	 * @param query Suchanfrage
	 * @param limit Anzahl der Suchergebnisse
	 * @param source Gibt die Quellen an, die in der Anfrage berücksichtig werden sollen. 
	 * Sollen mehrere Quellen verwendet werden, kann dieser Parameter mehrfach angegeben werden.
	 * Mögliche Werte: facebook, eventful, upcoming
	 * @throws EVDBRuntimeException
	 * @throws IOException
	 * @throws SocketTimeoutException
	 * 
	 */
	public void lookup(String query, int limit, List<Carrier> sourcelist)
			throws EVDBRuntimeException, SocketTimeoutException, IOException {

		URLFetchService fetcher = URLFetchServiceFactory.getURLFetchService();
		ArrayList<Future<HTTPResponse>> asyncResponses = new ArrayList<Future<HTTPResponse>>();
		if(sourcelist.isEmpty()) sourcelist=Arrays.asList(Carrier.values());
		for(Carrier c: sourcelist){
			URL url = new URL(c.getURL(limit, query));
			Future<HTTPResponse> responseFuture = fetcher.fetchAsync(url);
			asyncResponses.add(responseFuture);
		}
		HTTPResponse response = null;
		byte[] content = null;
		for (Future<HTTPResponse> future : asyncResponses) {
			try {
				response = future.get();
				content = response.getContent();
				model.getJenaModel().read(new ByteArrayInputStream(content),"UTF-8");
			}
			catch (InterruptedException e) {
				e.printStackTrace();
			}
			catch (ExecutionException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * schreibt einen Fehler auf den Outputstream (bei Timeout)
	 * 
	 * @param out Outputstream, auf dem Ausgabe erfolgen soll
	 * @param query Suchbegriffe
	 * @param source Quellen
	 * @param format Ausgabeformat
	 * Führt nicht immer zu einer Ausgabe, da bei asynchroner Anfrage die Anfrage bei einem einzelnen Timout nicht abbricht, 
	 * sondern stattdessen die Ergebnisse der erfolgreichen Anfragen angezeigt.
	 */
	public static void writeError(OutputStream out, String query,
			String format) {
		String url = "http://kit-services.appspot.com/event?limit=20&source=facebook&format=" + format + "&query=" + query;
		String error = "Time-Out-Error beim bearbeiten Ihrer Anfrage! \n"
				+ "Bitte versuchen Sie es probehalber mit folgender URL: "
				+ url
				+ "\n"
				+ "Setzen Sie zur Not den Parameter &limit noch weiter herunter. ";
		byte[] er = error.getBytes();
		try {
			out.write(er);
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}

	/**
	 * Erzeugt eine Map der Parameter des HttpServletRequests
	 * 
	 * @param request Der angefragte Request
	 * @return Map der Parameter <Schlüssel, Wert> (jeweils String)
	 */
	public static Map<String, String> parameters(HttpServletRequest request) {
		String query = null;
		try {
			query = URLEncoder.encode(request.getParameter("query"), "UTF-8");
		}
		catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		String format = request.getParameter("format");
		// Falls Format-Parameter nicht gesetzt
		if (format == null)
			format = format_HTML_map;
		
		//Falls Limit nicht gesetzt
		String limit = request.getParameter("limit");
		if (limit == null)
			limit = standartLimit;
		//Map erzeugen und ausgeben
		HashMap<String, String> map = new HashMap<String, String>();
		map.put("query", query);
		map.put("format", format);
		map.put("limit", limit);
		return map;
	}
}
